// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © ChartPrime

//@version=6
indicator('Heatmap VWAP Profile [ChartPrime]', max_bars_back = 5000, overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_polylines_count = 100)


// --------------------------------------------------------------------------------------------------------------------}
// 📌 𝙐𝙎𝙀𝙍 𝙄𝙉𝙋𝙐𝙏𝙎
// --------------------------------------------------------------------------------------------------------------------{

anchorBars = input.int(300, 'Anchor (bars back)', minval = 1, maxval = 500)
showBands = input.bool(true, 'Show Dynamic Polyline Bands')
maxStdevMult = input.float(3.0, 'Max StdDev Outer Range (±σ)', minval = 0.5, step = 0.5)

// Volume Profile inputs
showVP = input.bool(true, 'Show Volume Profile')
vpRows = input.int(70, 'Volume Profile Rows / Bands Count', minval = 2, maxval = 100)
vpWidth = input.int(60, 'Volume Profile Max Width (bars)', minval = 10)

// Label Inputs
showLabels = input.bool(true, 'Show Price Labels', group = 'Labels')
labelColor = input.color(color.gray, 'Labels Background Color', group = 'Labels')
labelTextColor = input.color(color.white, 'Labels Text Color', group = 'Labels')

// Harmonized VWAP & Bin Color Palette Setup
color colVwap = #FFB300 // Main Anchor VWAP: Bright Gold

// Polyline Bands & Up-Volume Profile Bin Gradients (Low -> High Volume)
color colBandLow = #FFE082 // Low Volume Node: Soft Muted Gold
color colBandHigh = #FF6D00 // High Volume Node: Vivid Deep Amber/Orange

// Down-Volume Profile Bin Gradient (Soft Tan -> Burnt Bronze)
color vpDnLow = #D7CCC8 // Low Volume Down: Warm Soft Tan
color vpDnHigh = #8D6E63 // High Volume Down: Deep Burnt Bronze

// Opacity Controls
minTransp = input.int(10, 'High Volume Opacity Transp (0 = Solid)', minval = 0, maxval = 100)
maxTransp = input.int(85, 'Low Volume Opacity Transp (100 = Invisible)', minval = 0, maxval = 100)


// --------------------------------------------------------------------------------------------------------------------}
// 📌 𝙄𝙉𝘿𝙄𝘾𝘼𝙏𝙊𝙍 𝘾𝘼𝙇𝘾𝙐𝙇𝘼𝙏𝙄𝙊𝙉𝙎
// --------------------------------------------------------------------------------------------------------------------{

// Anchor Index Setup
anchorIdx = math.max(0, last_bar_index - anchorBars)
isAnchored = bar_index >= anchorIdx

// Anchored VWAP Calculation Variables
var float cumPV = 0.0
var float cumVol = 0.0
var float cumPV2 = 0.0

if bar_index == anchorIdx
    cumPV := 0.0
    cumVol := 0.0
    cumPV2 := 0.0
    cumPV2

typical = hlc3
if isAnchored
    cumPV := cumPV + typical * volume
    cumVol := cumVol + volume
    cumPV2 := cumPV2 + typical * typical * volume
    cumPV2

vwap = cumVol > 0 ? cumPV / cumVol : na
vwap2 = cumVol > 0 ? cumPV2 / cumVol : na
variance = vwap2 - vwap * vwap
stdv = variance > 0 ? math.sqrt(variance) : 0.0

// Dynamic Storage for Polyline Points
var array<chart.point> bandPoints = array.new<chart.point>()

if bar_index == anchorIdx
    array.clear(bandPoints)

// Store Polyline Chart Points
if isAnchored and not na(vwap)
    float stepStdev = 2.0 * maxStdevMult / vpRows

    for r = 0 to vpRows by 1
        float mult = -maxStdevMult + r * stepStdev
        float bandVal = vwap + mult * stdv
        array.push(bandPoints, chart.point.from_index(bar_index, bandVal))


// --------------------------------------------------------------------------------------------------------------------}
// 📌 𝙑𝙄𝙎𝙐𝘼𝙇𝙄𝙕𝘼𝙏𝙄𝙊𝙉
// --------------------------------------------------------------------------------------------------------------------{

// Core VWAP Plot
plot(vwap, 'VWAP', color = colVwap, linewidth = 2)

// Dynamic Object Arrays
var array<polyline> polylines = array.new<polyline>()
var array<box> vpBoxes = array.new<box>()
var array<label> binLabels = array.new<label>()

if barstate.islast and isAnchored
    // Clear old drawings
    if array.size(polylines) > 0
        for i = 0 to array.size(polylines) - 1 by 1
            polyline.delete(array.get(polylines, i))
        array.clear(polylines)

    if array.size(vpBoxes) > 0
        for i = 0 to array.size(vpBoxes) - 1 by 1
            box.delete(array.get(vpBoxes, i))
        array.clear(vpBoxes)

    if array.size(binLabels) > 0
        for i = 0 to array.size(binLabels) - 1 by 1
            label.delete(array.get(binLabels, i))
        array.clear(binLabels)

    hiPrice = vwap + maxStdevMult * stdv
    loPrice = vwap - maxStdevMult * stdv

    // 1. Volume Profile Calculations
    array<float> upVol = array.new<float>(vpRows, 0.0)
    array<float> dnVol = array.new<float>(vpRows, 0.0)
    array<float> totVol = array.new<float>(vpRows, 0.0)
    float maxVol = 0.0
    int pocRow = 0

    if hiPrice > loPrice
        float rowSize = (hiPrice - loPrice) / vpRows

        for i = 0 to anchorBars - 1 by 1
            if bar_index - i >= anchorIdx
                float bHigh = high[i]
                float bLow = low[i]
                float bVol = volume[i]
                bool isUp = close[i] >= open[i]

                int rowLo = math.max(0, int(math.floor((bLow - loPrice) / rowSize)))
                int rowHi = math.min(vpRows - 1, int(math.floor((bHigh - loPrice) / rowSize)))
                int span = rowHi - rowLo + 1

                if span > 0
                    float volPerRow = bVol / span
                    for r = rowLo to rowHi by 1
                        if isUp
                            array.set(upVol, r, array.get(upVol, r) + volPerRow)
                        else
                            array.set(dnVol, r, array.get(dnVol, r) + volPerRow)

        for r = 0 to vpRows - 1 by 1
            float tot = array.get(upVol, r) + array.get(dnVol, r)
            array.set(totVol, r, tot)
            if tot > maxVol
                maxVol := tot
                pocRow := r
                pocRow

    // 2. Volume Profile Boxes Rendering
    if showVP and maxVol > 0 and hiPrice > loPrice
        float rowSize = (hiPrice - loPrice) / vpRows
        int xLeft = bar_index

        for r = 0 to vpRows - 1 by 1
            float up = array.get(upVol, r)
            float dn = array.get(dnVol, r)
            float tot = array.get(totVol, r)

            if tot > 0
                float rowLow = loPrice + r * rowSize
                float rowHigh = rowLow + rowSize
                int upW = int(math.round(vpWidth * up / maxVol))
                int dnW = int(math.round(vpWidth * dn / maxVol))

                int currentTransp = int(math.round(maxTransp - tot / maxVol * (maxTransp - minTransp)))

                color baseUpCol = color.from_gradient(tot, 0.0, maxVol, colBandLow, colBandHigh)
                color baseDnCol = color.from_gradient(tot, 0.0, maxVol, vpDnLow, vpDnHigh)

                color dynamicUpCol = color.new(baseUpCol, currentTransp)
                color dynamicDnCol = color.new(baseDnCol, currentTransp)

                if upW > 0
                    array.push(vpBoxes, box.new(left = xLeft, top = rowHigh, right = xLeft + upW, bottom = rowLow, bgcolor = dynamicUpCol, border_color = chart.bg_color))
                if dnW > 0
                    array.push(vpBoxes, box.new(left = xLeft + upW, top = rowHigh, right = xLeft + upW + dnW, bottom = rowLow, bgcolor = dynamicDnCol, border_color = chart.bg_color))

    // 3. Dynamic Heatmap Polylines Rendering
    int cols = vpRows + 1
    int totalPoints = array.size(bandPoints)
    int numBars = totalPoints / cols

    if showBands and numBars > 0
        for r = 0 to vpRows by 1
            array<chart.point> linePoints = array.new<chart.point>()
            for b = 0 to numBars - 1 by 1
                int idx = b * cols + r
                array.push(linePoints, array.get(bandPoints, idx))

            float lineVol = 0.0
            if maxVol > 0
                if r == 0
                    lineVol := array.get(totVol, 0)
                    lineVol
                else if r == vpRows
                    lineVol := array.get(totVol, vpRows - 1)
                    lineVol
                else
                    lineVol := (array.get(totVol, r - 1) + array.get(totVol, r)) / 2.0
                    lineVol

            float binRatio = maxVol > 0 ? lineVol / maxVol : 0.0

            color mixedBaseCol = color.from_gradient(lineVol, 0.0, maxVol, colBandLow, colBandHigh)
            int currentTransp = int(math.round(maxTransp - binRatio * (maxTransp - minTransp)))
            color dynamicLineCol = color.new(mixedBaseCol, currentTransp)
            dynamicWidth = binRatio > 0.75 ? line.style_solid : line.style_dotted

            polyline pl = polyline.new(linePoints, line_color = dynamicLineCol, line_width = 1, line_style = dynamicWidth)
            array.push(polylines, pl)

    // 4. Bin Labels Rendering (VWAP, POC, TOP, BOTTOM)
    if showLabels and hiPrice > loPrice
        float rowSize = (hiPrice - loPrice) / vpRows
        float pocPrice = loPrice + (pocRow + 0.5) * rowSize
        int labelX = bar_index

        // Top Range (+Max σ)
        array.push(binLabels, label.new(x = labelX, y = hiPrice, text = 'TOP (+' + str.tostring(maxStdevMult, '#.#') + 'σ): ' + str.tostring(hiPrice, '#.##'), color = color.new(colBandHigh, 20), textcolor = chart.fg_color, style = label.style_label_left, size = size.small))

        // VWAP Label
        array.push(binLabels, label.new(x = labelX, y = vwap, text = 'VWAP: ' + str.tostring(vwap, '#.##'), color = colVwap, textcolor = color.black, style = label.style_label_left, size = size.small))

        // POC (Point of Control)
        array.push(binLabels, label.new(x = labelX + vpWidth, y = pocPrice, text = 'POC: ' + str.tostring(pocPrice, '#.##'), color = color.new(#FF3D00, 10), textcolor = chart.fg_color, style = label.style_label_left, size = size.small))

        // Bottom Range (-Max σ)
        array.push(binLabels, label.new(x = labelX, y = loPrice, text = 'BOT (-' + str.tostring(maxStdevMult, '#.#') + 'σ): ' + str.tostring(loPrice, '#.##'), color = color.new(colBandLow, 20), textcolor = color.black, style = label.style_label_left, size = size.small))

// --------------------------------------------------------------------------------------------------------------------}
